Fork me on GitHub

SPRING AOP 续

注意:所有文章除特别说明外,转载请注明出处.

AOP(面向切面(方面)编程)

1.使用AOP原因

1.在开发完核心业务程序之后,可能还会遇到非核心业务程序(如:权限校验,记录日志等)。
2.如果在原有程序基础上添加新功能程序就会出现代码混乱等情况。

动态代理实现的过程,这里可理解AOP是,将核心业务代码过程比作一个柱体,其他的日志记录,权限校验等就像是横切核心业务的面,这些面需要完成一些非核心的业务。然后定义的这些切面各自完成多个非核心的业务。

所以这是一种通过预编译方式和运行期动态代理实现在不修改源码的情况下给程序动态统一添加功能的技术。

2.AOP与OOP关系


AOP与Java无缝对接的是一种称为AspectJ的技术,Spring AOP与AspectJ实现原理上并不完全一致,但功能上是相似的。AOP的出现确实解决外围业务代码与核心业务代码分离的问题,但它并不会替代OOP,如果说OOP的出现是把编码问题进行模块化,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。

2.1 AOP与OOP的关系

OOP是针对业务处理过程的实体及其属性进行抽象封装,从而获得更加清晰的逻辑单元划分。AOP针对业务处理中的方法的切面(增加更细方法)进行抽取。

1.AOP概念

  • 1)面向切面编程,扩展功能不修改源程序实现
  • 2)AOP采取横向抽取机制,取代了传统纵向继承体系(纵向抽取机制:一个类文件继承另一个类文件的机制)重复性代码
  • 通知(advice) 定义要织入目标对象的逻辑。这些通知包括前置通知,后置通知,返回通知以及异常和环绕通知等。
  • 切点(pointcut) 如果我们将通知定义是在何时执行通知,那么切点就是定义在何处执行通知。所以切点的作用是通过匹配规则查找合适的连接点(joinpoint),然后aop会在这些连接点上织入通知。
  • 切面(aspect) 切面包含了通知和切点,通知和切点共同定义了切面是什么,在何时,何处执行切面逻辑。

2.AOP原理

AOP(横向抽取机制) 底层使用动态代理方式实现(实现过程)。

1.代理模式

我们可以清楚的知道代理类实现了被代理类的接口,同时与被代理类是组合关系。

2.静态代理

1.接口类

interface Person{
    void speak();
}   

2.实体类

class Actor implements Person {
    private String content;
    public Actor(String content){
        this.content = content;
    }

    @Override
    public void speak(){
        System.out.println(this.content);
    }
}

3.代理类

class Agent implements Person {
    private Actor actor;
    private String before;
    private String after;

    public Agent(Actor actor, String before, String after) {
        this.actor = actor;
        this.before = before;
        this.after = after;
    }

    @Override 
    public void speak(){
        //在speak()之前执行
        System.out.println("Before actor speak, Agent say: " + before);
        //执行speak()
        this.actor.speak();
        //执行speak()之后执行
        System.out.println("After actor speak, Agent say: " + after);
    }
}

4.测试类

public class StaticProxy {
    public static void main(String[] args){
        Actor actor = new Actor("actor");
        Agent agent = new Agent(actor, "agent", "good");
        agent.speak();
    }
}

2.动态代理实现

这里动态代理技术的实现表示将在相关方法之后或者之前加上另外一些对应的方法,使得在不改变原程序的条件下能够动态的实现另外的一些操作。

2.1 动态代理InvocationHandler与Proxy

在Java动态代理机制中有两个重要的类和接口,InvocationHandler(接口)和Proxy(类),这类和接口是动态代理实现的核心。

1.InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。

注意:每个动态代理类的调用程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。

//proxy:被代理类的实例
//method:所要调用某个对象真实的方法的Method对象
//args:指代代理对象方法传递的参数

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

2.Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。

//1.loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
//2.interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
//3.h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

1.动态代理实现

package cn.edu.xidian.see.*;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//1.实现InvocationHandler接口,并且可以初始化被代理类的对象
public class MyProxy implements InvocationHandler {

    //1.1 目标类对象
    private Object targetObject;

    public Object createProxyInstance(Object targetObject){
        this.targetObject = targetObject;
        //2.返回一个被修改过的实例
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        command();
        Object ret = method.invoke(targetObject,args);
        award();

        return ret;
    }

    private void command(){
        System.out.println("驯兽师发命令");
    }

    private void award(){
        System.out.println("驯兽师给奖励");
    }
}

3.原先方法以及动态代理实现之后方法的调用

package cn.edu.xidian.see.*;

import cn.edu.xidian.see.*.impl.Dog;

public class Client {
    public static void main(String[] args) {
        MyProxy hander = new MyProxy();
        Animal dog = (Animal) hander.createProxyInstance(new Dog());
        dog.run();
        dog.jump();
    }
}

1.JDK动态代理

2.使用cglib生成代理
  对于不使用接口的业务类,无法使用jdk动态代理,而cglib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题

    2.1.关于intercept拦截方法
         /**
         * @param obj CGlib根据指定父类生成的代理对象
         * @param method 拦截的方法
         * @param args 拦截方法的参数数组
         * @param proxy 方法的代理对象,用于执行父类的方法
         * @return
        */
        public Object intercept(Object obj, Method method, Object[] args,
               MethodProxy proxy) throws Throwable {
            ... ...
        }

注意:最新版本Spring已经将CGLib开发类引入spring-core-3.2.0.RELEASE.jar

总结:Spring AOP底层就是通过JDK动态代理或CGLIB动态代理技术为目标Bean执行横向织入

1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

  程序中应该优先对接口创建代理,便于程序解耦维护

  标记为final的方法,不能被代理,因为无法进行覆盖

JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰
CGLib 是针对目标类生产子类,因此类或方法不能是final的

  Spring只支持方法连接点,不提供属性连接

3.AOP底层使用动态代理实现

  • 第一种情况,有接口的情况,使用动态代理创建接口实现类代理对象。
  • 第二种情况,没有接口的情况,使用动态代理创建类的子类代理对象。

第一种情况:使用JDK动态代理,针对有接口的情况

步骤:1.定义一个包含切面逻辑的对象。2.定义一个advice对象,实现InvocationHandler接口,并且将过程1的切面逻辑对象和目标对象传入。3.将上面的advice对象和目标对象传给JDK动态代理方法,为目标对象生成代理。

public interface Dao{
    public void add();
}

public class DaoImpl implements Dao{
    public void add(){
        //添加实现逻辑
    }
}

注意:使用动态代理方式,创建接口实现类代理对象 (创建和DaoImpl类平级的对象,这个对象不是真正的对象,是一个代理对象,实现和DaoImpl相同的功能)。

第二种情况:使用cglib动态代理,针对没有接口的情况

public class User{
    public void add(){

    }
}

动态代理实现(使用cglib动态代理,针对没有接口的情况)

  • 创建User类的子类的代理对象
  • 在子类里面调用父类的方法完成增强

4.AOP操作术语

  • 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

  • 切面(Aspect):一个类,在里面定义了通知和切点。

  • PointCut(切入点):表达式,告诉程序要在执行哪些核心业务的时候,执行非核心的业务。

  • JoinPoint(连接点):类里面的哪些方法可以被增强,这些方法就成为连接点,所以这些连接点指的是方法

  • Advice(通知/增强):增强的逻辑,称为增强,比如扩展日志功能,这个日志功能就称为增强

    前置通知:在方法之前执行
    后置通知:在方法之后执行
    异常通知:方法出现异常执行
    最终通知:在后置之后执行
    环绕通知:在方法之前和之后执行

1.对应的注解

@After 通知方法会在目标方法返回或抛出异常后调用
  • Aspect(切面):将增强应用到具体方法上面,这一过程称为切面(将增强应用到切入点的过程就称为切面) 对横切关注点的抽象

切面:简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用横切系统,AOP把软件系统分为两个部分:核心关注点(业务处理的主要流程)和横切关注点。

2.切点表达式

Spring的AOP操作

1.在Spring里面进行AOP操作,使用aspectj实现
AspectJ:面向切面的框架,不是Spring的一部分,与Spring一起做AOP开发

2.使用aspectj实现AOP有两种方式
1)基于aspectj的xml配置
2)基于aspectj的注解方式

AOP操作准备

1.除了导入基本的jar包之外,还需要导入aop相关的jar包
2.创建一个Spring的核心配置文件,导入aop约束

使用表达式配置切入点

1.切入点,实际增强的方法
2.常用表达式
execution

1)execution(*cn.itcast.aop.Book.add(..))
2)execution(*cn.itcast.aop.Book.*(..))
3)execution(**.*(..))
4)匹配所有save开头的方法execution(*save*(..))

使用AOP方式操作步骤

1.配置两个类的对象
<bean id="book" class="cn.itcast.aop.Book"></bean>
<bean id="myBook" class="cn.itcast.aop.MyBook"></bean>

2.配置aop操作(1.配置切入点(使用表达式的方式) 2.配置切面(将增强用到方法上面去)  expression:上面所诉表达式 id:名字)
<aop:config>
    <aop:pointcut expression="" id=""/>
    <aop:aspect ref="增强的对象">
    //配置增强类型
    <aop:before method="增强类里面所使用的方法" pointcut-ref="切入点的引用"/>    
    </aop:aspect>
</aop:config>

AOP案例

1.Dog.java类(接口与接口实现)

1.接口

package cn.edu.xidian.see.*;
public interface Animal {
    public void run();
    public void jump();
}

2.接口实现

package cn.edu.xidian.see.*.impl;

import cn.edu.xidian.see.*.Animal;

public class Dog implements Animal {
    @Override
    public void run(){
        System.out.println("狗跑");
    }

    @Override
    public void jump(){
        System.out.println("狗跳");
    }
}

2.

本文标题:SPRING AOP 续

文章作者:Bangjin-Hu

发布时间:2019年10月15日 - 09:22:26

最后更新:2020年03月30日 - 08:17:55

原始链接:http://bangjinhu.github.io/undefined/第3章 Spring - AOP 续一/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.